package main

import (
	"context"
	"fmt"
	"github.com/coreos/etcd/clientv3"
	"github.com/coreos/etcd/clientv3/concurrency"
	"log"
	"time"
)

/**
https://www.cnblogs.com/jiujuan/p/12147809.html
etcd如何实现分布式锁
1、raft
2、lease(租约机制，设置过期时间，当租约到期键值对就会被删除)
还支持续约
3、watch(监听某一个固定的key，当key发生变化，客户端会收到通知)
在实现分布式锁时，如果抢锁失败，可通过 Prefix 机制返回的 KeyValue 列表获得 Revision 比自己小且相差最小的 key（称为 pre-key），
对 pre-key 进行监听，因为只有它释放锁，自己才能获得锁，如果 Watch 到 pre-key 的 DELETE 事件，则说明pre-ke已经释放，自己已经持有锁
4、prefix(目录机制，通过前缀返回多个key-value,同时包含Revision，通过Revision的大小判断是否获取到锁)
5、Revision（每一个key都会带有一个Revision,每进行一次事务+1）
*/

func main() {
	cli, err := clientv3.New(clientv3.Config{
		Endpoints:            []string{"192.168.0.104:2379"},
		AutoSyncInterval:     0,
		DialTimeout:          3 * time.Second,
		DialKeepAliveTime:    0,
		DialKeepAliveTimeout: 0,
		MaxCallSendMsgSize:   0,
		MaxCallRecvMsgSize:   0,
		TLS:                  nil,
		Username:             "",
		Password:             "",
		RejectOldCluster:     false,
		DialOptions:          nil,
		LogConfig:            nil,
		Context:              nil,
		PermitWithoutStream:  false,
	})
	if err != nil {
		log.Fatal(err)
	}
	defer cli.Close()

	s1, err := concurrency.NewSession(cli)
	if err != nil {
		log.Fatal(err)
	}
	defer s1.Close()
	m1 := concurrency.NewMutex(s1, "/my-lock/")

	s2, err := concurrency.NewSession(cli)
	if err != nil {
		log.Fatal(err)
	}
	defer s2.Close()
	m2 := concurrency.NewMutex(s2, "/my-lock/")
	if err := m1.Lock(context.TODO()); err != nil {
		log.Fatal(err)
	}
	malocked := make(chan struct{})
	go func() {
		defer close(malocked)
		// 等待直到会话s1释放了/my-lock/的锁
		if err := m2.Lock(context.TODO()); err != nil {
			log.Fatal(err)
		}
		fmt.Println("m2 locked")
	}()
	if err := m1.Unlock(context.TODO()); err != nil {
		log.Fatal(err)
	}
	fmt.Println("released lock for s1")
	<-malocked
	fmt.Println("acquired lock for s2")
}
